﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvRide.Core.ObjectModel;
using CodeMadeSimple.Common.Validation;
using CodeMadeSimple.Common.Tests.Validation;
using NUnit.Framework;
using System.IO;
using System.Xml;
using CodeMadeSimple.PropertiesManagement;
using CodeMadeSimple.Common.Tests;

namespace EnvRide.Core.UnitTests
{
    public abstract class OverrideIncludeTests<T> : EnvRideBaseTest where T : OverrideInclude, new()
    {
        protected const string ARGS_PROPS_PFX_RET_TO_PREV_VALUE_MSG = "The properties prefix of the EnvRideProcessArguments instance should return to its previous value after executing";

        [Test]
        public void Process_NullEnvRideProcessArguments_ThrowArgumentNullException()
        {
            T overInc = new T();

            ExceptionAssert.AssertNullArgumentException("args", () => overInc.Process(null));
        }

        [Test]
        public void Process_NullProject_ThrowNullReferenceException()
        {
            T overInc = new T();

            EnvRideProcessArguments args = new EnvRideProcessArguments();

            Assert.Throws<NullReferenceException>(() => overInc.Process(args), "Should throw a NullPointerException when the Project property of the override include instance its null!", new [] { "The property \"Property\" cannot be null! The override include instance must belong to a Project!" });
        }

        protected void Validate(OverrideInclude inc)
        {
            ValidationResult result = inc.Validate();

            ValidationResult expectedResult = new ValidationResult(inc);
            expectedResult.AddMessage(new RequiredValueValidationMessage("EnvironmentName"));

            ValidationResultTests.AssertValidationResult(result, expectedResult);

            inc.EnvironmentName = "EnvironmentName";

            result = inc.Validate();

            expectedResult = new ValidationResult(inc);

            ValidationResultTests.AssertValidationResult(result, expectedResult);
        }

        protected void ProcessWithErrors(String filePath, OverrideInclude attOverInc, XmlDocument doc, EnvRideProcessArguments args)
        {
            attOverInc.OverrideName = "InvalidOver1";

            doc.Load(filePath);

            attOverInc.Process(args);

            AssertHasErrors(args, new[] { GetOverrideNotFoundMessage(attOverInc.OverrideName) });

            attOverInc.OverrideName = "InvalidOver1,InvalidOver2";

            doc.Load(filePath);

            Appender.Clear();

            attOverInc.Process(args);

            AssertHasErrors(args, new[] { GetOverrideNotFoundMessage("InvalidOver1"), GetOverrideNotFoundMessage("InvalidOver2") });

            attOverInc.EnvironmentName = "InvalidEnvironment";

            doc.Load(filePath);

            Appender.Clear();

            attOverInc.Process(args);

            AssertHasErrors(args, new[] { GetEnvironmentNotFoundMessage(attOverInc.EnvironmentName, attOverInc, args) });
        }

        public virtual void SerializeDeserializeAndCompare(T attOverInc)
        {
            T attOver2 = null;

            using (MemoryStream ms = new MemoryStream())
            {
                using (XmlWriter xmlW = XmlWriter.Create(ms))
                {
                    xmlW.WriteStartElement("AttributeOverrideInclude");

                    attOverInc.WriteXml(xmlW);

                    xmlW.WriteEndElement();
                }

                ms.Position = 0;

                using (XmlReader xmlR = XmlReader.Create(ms))
                {
                    xmlR.Read();

                    xmlR.Read();

                    attOver2 = new T();
                    attOver2.ReadXml(xmlR);
                }
            }

            AssertEquals(attOverInc, attOver2);
        }

        public virtual void AssertEquals(T attOver1, T attOver2)
        {
            Assert.AreEqual(attOver1.EnvironmentName, attOver2.EnvironmentName);
            Assert.AreEqual(attOver1.FilePath, attOver2.FilePath);
            Assert.AreEqual(attOver1.If, attOver2.If);
            Assert.AreEqual(attOver1.OverrideName, attOver2.OverrideName);
            Assert.AreEqual(attOver1.PropertiesPrefix, attOver2.PropertiesPrefix);
            Assert.AreEqual(attOver1.NewPropertyContainer, attOver2.NewPropertyContainer);
        }

        [Test]
        public virtual void SerializeAndDeserialize()
        {
            T overInc = new T();

            SerializeDeserializeAndCompare(overInc);

            overInc.EnvironmentName = "EnvName";
            overInc.FilePath = "FilePath";
            overInc.If = "If";
            overInc.OverrideName = "OverName";
            overInc.PropertiesPrefix = "PropPrefix";
            overInc.NewPropertyContainer = "PropContainer";

            SerializeDeserializeAndCompare(overInc);

            overInc.EnvironmentName = "EnvName1";
            overInc.FilePath = "FilePath2";
            overInc.If = "If3";
            overInc.OverrideName = "OverName4";
            overInc.PropertiesPrefix = "PropPrefix5";
            overInc.NewPropertyContainer = "PropContainer6";

            SerializeDeserializeAndCompare(overInc);
        }

        [Test]
        public void GetReferencedEnvironment_InvalidEnvironmentName_SetArgumentsHasErrorsAndLogError()
        {
            throw new NotImplementedException();
        }

        [Test]
        public void GetReferencedEnvironment_ValidEnvironmentName_ReturnsReferencedEnvironment()
        {
            EnvRideProcessArguments args = new EnvRideProcessArguments();

            T overInc = new T();

            EnvRideProject proj = new EnvRideProject();

            proj.Environments = new EnvironmentCollection();

            EnvRide.Core.ObjectModel.Environment env1 = new EnvRide.Core.ObjectModel.Environment { Name = "Env1" };
            proj.Environments.Add(env1);

            EnvRide.Core.ObjectModel.Environment env2 = new EnvRide.Core.ObjectModel.Environment { Name = "Env2" };
            proj.Environments.Add(env2);

            EnvRide.Core.ObjectModel.Environment env3 = new EnvRide.Core.ObjectModel.Environment { Name = "Env3" };
            proj.Environments.Add(env3);

            overInc.Project = proj;

            overInc.EnvironmentName = "Env1";

            Assert.AreSame(env1, overInc.GetReferencedEnvironment(proj, args), "Should return the environment with the name equals to the value in the property \"EnvironmentName\".");

            overInc.EnvironmentName = "Env3";

            Assert.AreSame(env3, overInc.GetReferencedEnvironment(proj, args), "Should return the environment with the name name equals to the value in the property \"EnvironmentName\".");

            PropertyContext ctx = new PropertyContext("Ctx");
            args.PropertyContainer = ctx;

            Property prop = new Property { Name = "EnvName", Value = "Env1" };
            ctx.AddProperty(prop);

            overInc.EnvironmentName = "${EnvName}";

            Assert.AreSame(env1, overInc.GetReferencedEnvironment(proj, args), "Should return the environment with the name equals to the referenced property value supplied in the property \"EnvironmentName\"");

            prop.Value = "Env3";

            Assert.AreSame(env3, overInc.GetReferencedEnvironment(proj, args), "Should return the environment with the name equals to the referenced property value supplied in the property \"EnvironmentName\"");
        }

        [Test]
        public void GetReferencedProject_FilePathNullOrEmpty_ReturnsOwnerProject()
        {
            EnvRideProcessArguments args = new EnvRideProcessArguments();

            T overInc = new T();

            EnvRideProject proj = new EnvRideProject();

            overInc.Project = proj;

            overInc.FilePath = null;

            Assert.AreSame(proj, overInc.GetReferencedProject(args), "Since the override include \"FilePath\" property is null, it should return the Project that owns the override include.");

            overInc.FilePath = "";

            Assert.AreSame(proj, overInc.GetReferencedProject(args), "Since the override include \"FilePath\" property is an Empty string, it should return the Project that owns the override include.");
        }

        [Test]
        public void GetReferencedProject_FilePathWithValidValue_ReturnsReferencedProject()
        {
            EnvRideProcessArguments args = new EnvRideProcessArguments();

            T overInc = new T();

            EnvRideProject proj = new EnvRideProject();

            overInc.Project = proj;

            overInc.FilePath = "OverrideIncludeTests1.envRide";

            EnvRideProject refProj = overInc.GetReferencedProject(args);

            Assert.AreNotSame(proj, refProj, "Since the override include \"FilePath\" property has a value, it should look for the supplied file path on the File system.");
            Assert.AreEqual("Env1", refProj.Environments[0].Name, "Should be the name of the first environment of the OverrideIncludeTests1.envRide file.");

            overInc.FilePath = "OverrideIncludeTests2.envRide";

            refProj = overInc.GetReferencedProject(args);

            Assert.AreNotSame(proj, refProj, "Since the override include \"FilePath\" property has a value, it should look for the supplied file path on the File system.");
            Assert.AreEqual("Env2", refProj.Environments[0].Name, "Should be the name of the first environment of the OverrideIncludeTests1.envRide file.");

            PropertyContext ctx = new PropertyContext("Ctx");
            args.PropertyContainer = ctx;

            Property prop = new Property { Name = "FilePath", Value = "OverrideIncludeTests1.envRide" };
            ctx.AddProperty(prop);

            overInc.FilePath = "${FilePath}";

            refProj = overInc.GetReferencedProject(args);

            Assert.AreNotSame(proj, refProj, "Since the override include \"FilePath\" property has a value, it should look for the supplied file path on the File system.");
            Assert.AreEqual("Env1", refProj.Environments[0].Name, "Should be the name of the first environment of the OverrideIncludeTests1.envRide file.");

            prop.Value = "OverrideIncludeTests2.envRide";

            refProj = overInc.GetReferencedProject(args);

            Assert.AreNotSame(proj, refProj, "Since the override include \"FilePath\" property has a value, it should look for the supplied file path on the File system.");
            Assert.AreEqual("Env2", refProj.Environments[0].Name, "Should be the name of the first environment of the OverrideIncludeTests2.envRide file.");
        }
    }
}
